package com.amadeus.commons.log.file.finder;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardOpenOption;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

import com.amadeus.commons.log.parser.SystemLogParser;

public class DirectoryWatcherService {

	private final WatchService watcher;
	private final Map<WatchKey, Path> keys;
	private boolean trace;
	private static DirectoryWatcherService watcherService;
	private Map<Path, SeekableByteChannel> channelMap;
	private SystemLogParser systemLogParser;

	@SuppressWarnings("unchecked")
	static <T> WatchEvent<T> cast(WatchEvent<?> event) {
		return (WatchEvent<T>) event;
	}

	public DirectoryWatcherService() throws IOException {
		this.watcher = FileSystems.getDefault().newWatchService();
		this.keys = new HashMap<WatchKey, Path>();
		this.trace = true;
		this.channelMap = new HashMap<>();
		this.systemLogParser = new SystemLogParser();
	}

	private void register(Path dir) throws IOException {
		WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		if (trace) {
			Path prev = keys.get(key);
			if (prev == null) {
				System.out.format("register: %s\n", dir);
			} else {
				if (!dir.equals(prev)) {
					System.out.format("update: %s -> %s\n", prev, dir);
				}
			}
		}
		keys.put(key, dir);
	}

	private void registerAll(final Path start) throws IOException {
		Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				register(dir);
				return FileVisitResult.CONTINUE;
			}
		});
	}

	public void processEvents() {
		for (;;) {

			WatchKey key;
			try {
				key = watcher.take();
			} catch (InterruptedException x) {
				return;
			}

			Path dir = keys.get(key);
			if (dir == null) {
				System.err.println("WatchKey not recognized!!");
				continue;
			}

			for (WatchEvent<?> event : key.pollEvents()) {
				WatchEvent.Kind<?> kind = event.kind();
				if (kind == OVERFLOW) {
					continue;
				}
				WatchEvent<Path> ev = cast(event);
				Path name = ev.context();
				Path child = dir.resolve(name);
				if (event.kind() == ENTRY_MODIFY && isTargetFile(child)) {
					updateChannelMap(child);
					// System.out.println(child + "\t" + channelMap.size());
				}
			}

			boolean valid = key.reset();
			if (!valid) {
				keys.remove(key);

				if (keys.isEmpty()) {
					break;
				}
			}
		}
	}

	private void updateChannelMap(Path path) {
		try {
			SeekableByteChannel byteChannel = channelMap.get(path);
			if (byteChannel == null) {
				System.out.println("creating channel object..");
				SeekableByteChannel seekableByteChannel = Files.newByteChannel(path, StandardOpenOption.READ);
				// seekableByteChannel.position(seekableByteChannel.size());
				seekableByteChannel.position(0);
				channelMap.put(path, seekableByteChannel);
			} else {
				handleFile(path, byteChannel);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void handleFile(Path path, SeekableByteChannel seekableByteChannel) {
		systemLogParser.parse(seekableByteChannel);
	}

	private boolean isTargetFile(Path child) {
		return child.getFileName().toString().matches("^.*server.*$");
	}

	public static synchronized DirectoryWatcherService getInstance() {
		if (null == watcherService) {
			try {
				watcherService = new DirectoryWatcherService();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return watcherService;
	}

	public void watch(Path dir, boolean recursive) {
		try {
			if (recursive) {
				System.out.format("Scanning %s ...\n", dir);
				registerAll(dir);
				System.out.println("Done.");
			} else {
				register(dir);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
